#ifndef INTEGER_HPP
#define	INTEGER_HPP

#include <limits>
#include <boost/operators.hpp>
#include <string>
#include <cassert>
#include "lex_cast.h"
#include "util.hpp"
using namespace std;


#define this_AND_rhs(PRED, CODE)	{if ((this->PRED()) && (rhs.PRED())) { CODE }}
#define this_OR_rhs(PRED, CODE)		{if ((this->PRED()) || (rhs.PRED())) { CODE }}


template <typename T>
T sgn(const T& x) {
	if (x > 0) return 1;
	if (x < 0) return -1;
	if (x == 0) return 0;
	return x; // matches indeterminates
}

/*
 * By inheriting from boost::arithmetic & co, we get alot of operators for free.
 * Fx we get operator+() for free by having implemented operator+=(), etc.
 * http://www.boost.org/doc/libs/1_48_0/libs/utility/operators.htm
 */

template <typename T>
class Integer 
:	boost::arithmetic< Integer<T> >, 
	boost::partially_ordered< Integer<T> >,
	boost::unit_steppable< Integer<T> >,
	boost::equality_comparable< Integer<T> >
{
private:
	enum special_t {POSINF, NEGINF, IND};
public:
	Integer() 
	{
		this->data.special = false;
		this->data.value.plain = 0;
	}
	
	Integer(T arg) 
	{
		this->data.special = false;
		this->data.value.plain = arg;
	}

	Integer(const Integer& arg) 
	:	data(arg.data)
	{
	}

	Integer& operator +=(const Integer& rhs) {
		this_AND_rhs(is_plain,	{this->data.value.plain += rhs.plain(); return *this;});
//		this_AND_rhs(is_posinf, {this->set(POSINF); return *this;});
//		this_AND_rhs(is_neginf, {this->set(NEGINF); return *this;});
		this_OR_rhs(is_ind,		{this->set(IND); return *this;});

		if (this->is_posinf() && rhs.is_neginf()) {this->set(IND); return *this;}
		if (this->is_neginf() && rhs.is_posinf()) {this->set(IND); return *this;}
		
		if (this->is_special() && rhs.is_plain()) {return *this;}
		if (this->is_plain() && rhs.is_special()) {this->set(rhs.data.value.symbolic); return *this;}
		
//		assert(false);
		return *this;
	}	
	
	Integer& operator -=(const Integer& rhs) {
		this_AND_rhs(is_plain,	{this->data.value.plain -= rhs.plain(); return *this;});
		this_AND_rhs(is_posinf, {this->set(IND); return *this;});
		this_AND_rhs(is_neginf, {this->set(IND); return *this;});
		this_OR_rhs(is_ind,		{this->set(IND); return *this;});

		if (this->is_posinf() && rhs.is_neginf()) {this->set(POSINF); return *this;}
		if (this->is_neginf() && rhs.is_posinf()) {this->set(NEGINF); return *this;}
		
		if (this->is_special() && rhs.is_plain()) {return *this;}
		if (this->is_plain() && rhs.is_special()) {this->set((-rhs).data.value.symbolic); return *this;}
		
//		assert(false);
		return *this;
	}

	Integer& operator *=(const Integer& rhs) {
		this_AND_rhs(is_plain,	{this->data.value.plain *= rhs.plain(); return *this;});
		this_AND_rhs(is_posinf, {this->set(POSINF); return *this;});
		this_AND_rhs(is_neginf, {this->set(POSINF); return *this;});
		this_OR_rhs(is_ind,		{this->set(IND); return *this;}); // we say 0*IND == IND
		
		if (this->is_posinf() && rhs.is_neginf()) {this->set(NEGINF); return *this;}
		if (this->is_neginf() && rhs.is_posinf()) {this->set(NEGINF); return *this;}
		
		bool inf_plain = (this->is_Abs_inf() && rhs.is_plain());
		bool plain_inf = (this->is_plain() && rhs.is_Abs_inf());
		const Integer *inf; 
		const Integer *plain;
		if (inf_plain) {inf = this; plain = &rhs;}
		if (plain_inf) {plain = this; inf = &rhs;}
		if (inf_plain || plain_inf) 
		{
			assert(inf->is_Abs_inf());
			assert(plain->is_plain());
			
			if (plain->plain() == 0) {
				*this = 0;
				return *this;
			}
			
			if (inf->is_positive() && plain->is_positive()) {this->set(POSINF); return *this;}
			if (inf->is_negative() && plain->is_negative()) {this->set(POSINF); return *this;}
			if (inf->is_positive() && plain->is_negative()) {this->set(NEGINF); return *this;}
			if (inf->is_negative() && plain->is_positive()) {this->set(NEGINF); return *this;}
			assert(false);
		}
		return *this;		
	}

	Integer& operator /=(const Integer& rhs) {
		if (rhs.is_plain() && rhs.plain() == 0) { // check for division by zero
			this->set(IND); // make us indeterminate
			return *this;
		}
		
		this_AND_rhs(is_plain,	{this->data.value.plain /= rhs.plain(); return *this;});
		this_AND_rhs(is_posinf, {this->set(IND); return *this;});
		this_AND_rhs(is_neginf, {this->set(IND); return *this;});
		this_OR_rhs(is_ind,		{this->set(IND); return *this;});

		if (this->is_plain() && rhs.is_Abs_inf()) {
			*this = 0;
			return *this;
		}
		
		if (rhs.is_plain()) {
			if (this->is_posinf() && rhs.is_positive()) {this->set(POSINF); return *this;}
			if (this->is_posinf() && rhs.is_negative()) {this->set(NEGINF); return *this;}
			if (this->is_neginf() && rhs.is_positive()) {this->set(NEGINF); return *this;}
			if (this->is_neginf() && rhs.is_negative()) {this->set(POSINF); return *this;}
			assert(false);
		}
		
		return *this;
	}
	
	Integer& operator ++() { // prefix
		(*this) += 1;
		return *this;
	}
	
	Integer& operator --() { // prefix
		(*this) -= 1;
		return *this;
	}
	
	bool operator ==(const Integer& rhs) const {
		this_AND_rhs(is_plain,	{return (this->plain() == rhs.plain());});
		/*
		 * Comparing two indeterminates yields false as they may come from two 
		 * different computations; hence their result may not be the same.
		 * Use is_ind() to check if two Integers are both indeterminate; note 
		 * how this is different than comparing their _values_ with ==.
		 */
		this_OR_rhs(is_ind,		{return false;}); // comparision involving IND is always false 
		this_AND_rhs(is_posinf, {return true;});
		this_AND_rhs(is_neginf, {return true;});
		return false;
	}
	
	bool operator <(const Integer& rhs) const {
		this_AND_rhs(is_plain,	{return (this->plain() < rhs.plain());});
		this_OR_rhs(is_ind,		{return false;}); // comparision involving IND is always false
		this_AND_rhs(is_posinf, {return false;});
		this_AND_rhs(is_neginf, {return false;});
		if (this->is_posinf() && rhs.is_plain()) return false;
		if (this->is_neginf() && rhs.is_plain()) return true;
		if (this->is_plain() && rhs.is_neginf()) return false;
		if (this->is_plain() && rhs.is_posinf()) return true;
		if (this->is_negative() && rhs.is_positive()) return true;
		if (this->is_positive() && rhs.is_negative()) return false;
		assert(false);
		return false;
	}
	
	Integer operator -() const {
		if (this->is_plain()) return Integer(-this->plain());
		if (this->is_posinf()) return Integer::create(NEGINF);
		if (this->is_neginf()) return Integer::create(POSINF);
		return *this; // IND
	}
	
	bool is_positive() const {
		if (this->is_plain()) return (this->plain() > 0);
		if (this->is_neginf()) return false;
		if (this->is_posinf()) return true;
		return false; // IND is neither positive nor negative 
	}
	bool is_negative() const {
		if (this->is_plain()) return (this->plain() < 0);
		if (this->is_neginf()) return true;
		if (this->is_posinf()) return false;
		return false; // IND is neither positive nor negative 
	}
	inline T plain() const {
		assert(this->is_plain());
		return this->data.value.plain;
	}
	bool is_special() const {
		return this->data.special;
	}
	bool is_plain() const {
		return !(this->is_special());
	}
	bool is_posinf() const {
		return (this->data.special && this->data.value.symbolic == POSINF);
	}
	bool is_neginf() const {
		return (this->data.special && this->data.value.symbolic == NEGINF);
	}
	bool is_Abs_inf() const {
		return (this->is_posinf() || this->is_neginf());
	}
	bool is_ind() const {
		return (this->data.special && this->data.value.symbolic == IND);
	}
	bool either_is_indeterminate(const Integer& rhs) const {
		return (this->is_ind() || rhs.is_ind());
	}

	string print() const
	{
		if (!this->data.special) {
			return ::stringify(this->data.value.plain);
		} else {
			switch(this->data.value.symbolic) {
				case POSINF:	return("+INF");
				case NEGINF:	return("-INF");
				case IND:		return("IND");
			}
		}
		return("?");
	}

	static Integer inf() {
		return Integer::create(POSINF);
	}
	static Integer ind() {
		return Integer::create(IND);
	}
	
protected:
	struct data_t {
		bool special;
		union value_t {
			T plain;
			special_t symbolic;
		} value;
	} data;

	static Integer create(Integer::special_t symb) {
		Integer ret;
		ret.data.special = true;
		ret.data.value.symbolic = symb;
		return ret;
	}

	inline void set(Integer::special_t symb) {
		this->data.special = true;
		this->data.value.symbolic = symb; 
	}
};

template <typename T>
bool operator<(const T& lhs, const Integer<T>& rhs) {
	Integer<T> lhs_wrapped(lhs);
	return (lhs_wrapped < rhs);
}

template <typename T>
bool operator==(const T& lhs, const Integer<T>& rhs) {
	Integer<T> lhs_wrapped(lhs);
	return (lhs_wrapped == rhs);
}

/**
 * This is ensures that boost::interval won't raise assertion failure when dividing intervals of Integers
 */
namespace std {
	template<typename T>
	struct numeric_limits< Integer<T> > : public numeric_limits<T>
	{
		static const bool has_infinity = true;
		static Integer<T> infinity() throw() {
			return  Integer<T>::inf();
		}
	};
}
	
template <typename T>
std::ostream& operator<<(std::ostream& stream, const Integer<T>& arg)
{
	stream << arg.print();
	return stream;
}

#endif	/* INTEGER_HPP */
